import sys
import os
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
                             QHBoxLayout, QLabel, QPushButton, QFileDialog, QSlider,
                             QSpinBox, QDoubleSpinBox, QComboBox, QGroupBox, QSplitter,
                             QMessageBox, QAction, QToolBar, QStatusBar, QDockWidget, QLineEdit, QListWidget)
from PyQt5.QtGui import QPixmap, QImage, QPainter, QPen, QColor
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QRect, QPoint, QSize, QTimer
import cv2
import numpy as np
from datetime import datetime
import imutils

class ImageProcessor:
    def __init__(self):
        self.original_image = None
        self.processed_image = None
        self.file_path = None
        self.drawing_rect = False
        self.rect_start = QPoint()
        self.rect_end = QPoint()
        self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

    def load_image(self, file_path):
        self.file_path = file_path
        self.original_image = cv2.imread(file_path)
        if self.original_image is None:
            return False
        self.processed_image = self.original_image.copy()
        return True

    def get_qimage(self, image=None):
        if image is None:
            image = self.processed_image
        if image is None:
            return QImage()

        rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb_image.shape
        bytes_per_line = ch * w
        convert_to_Qt_format = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)
        return convert_to_Qt_format.copy()

    def rotate(self, angle):
        if self.processed_image is not None:
            self.processed_image = imutils.rotate(self.processed_image, angle)
            return True
        return False

    def scale(self, factor):
        if self.processed_image is not None:
            h, w = self.processed_image.shape[:2]
            new_size = (int(w * factor), int(h * factor))
            self.processed_image = cv2.resize(self.processed_image, new_size, interpolation=cv2.INTER_AREA)
            return True
        return False

    def crop(self, rect):
        if self.processed_image is not None:
            x, y, w, h = rect.x(), rect.y(), rect.width(), rect.height()
            if w > 0 and h > 0:
                self.processed_image = self.processed_image[y:y + h, x:x + w]
                return True
        return False

    def adjust_brightness_contrast(self, brightness=0, contrast=0):
        if self.processed_image is not None:
            if brightness != 0:
                if brightness > 0:
                    shadow = brightness
                    highlight = 255
                else:
                    shadow = 0
                    highlight = 255 + brightness
                alpha_b = (highlight - shadow) / 255
                gamma_b = shadow

                buf = cv2.addWeighted(self.processed_image, alpha_b,
                                      self.processed_image, 0, gamma_b)
            else:
                buf = self.processed_image.copy()

            if contrast != 0:
                f = 131 * (contrast + 127) / (127 * (131 - contrast))
                alpha_c = f
                gamma_c = 127 * (1 - f)

                buf = cv2.addWeighted(buf, alpha_c,
                                      buf, 0, gamma_c)
            self.processed_image = buf
            return True
        return False

    def adjust_saturation(self, value):
        if self.processed_image is not None:
            hsv = cv2.cvtColor(self.processed_image, cv2.COLOR_BGR2HSV)
            h, s, v = cv2.split(hsv)
            s = np.clip(s * value, 0, 255).astype(hsv.dtype)
            hsv = cv2.merge([h, s, v])
            self.processed_image = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
            return True
        return False

    def smooth(self, method, kernel_size=3):
        if self.processed_image is not None:
            if method == 'Gaussian':
                self.processed_image = cv2.GaussianBlur(self.processed_image, (kernel_size, kernel_size), 0)
            elif method == 'Median':
                self.processed_image = cv2.medianBlur(self.processed_image, kernel_size)
            elif method == 'Bilateral':
                self.processed_image = cv2.bilateralFilter(self.processed_image, 9, 75, 75)
            return True
        return False

    def detect_shapes(self):
        if self.processed_image is not None:
            output = self.processed_image.copy()
            gray = cv2.cvtColor(self.processed_image, cv2.COLOR_BGR2GRAY)
            blurred = cv2.GaussianBlur(gray, (5, 5), 0)
            thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]

            contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                                           cv2.CHAIN_APPROX_SIMPLE)

            for c in contours:
                M = cv2.moments(c)
                if M["m00"] != 0:
                    cX = int(M["m10"] / M["m00"])
                    cY = int(M["m01"] / M["m00"])

                    epsilon = 0.04 * cv2.arcLength(c, True)
                    approx = cv2.approxPolyDP(c, epsilon, True)

                    if len(approx) == 3:
                        shape = "Triangle"
                    elif len(approx) == 4:
                        (x, y, w, h) = cv2.boundingRect(approx)
                        ar = w / float(h)
                        shape = "Square" if ar >= 0.95 and ar <= 1.05 else "Rectangle"
                    elif len(approx) == 5:
                        shape = "Pentagon"
                    elif len(approx) == 6:
                        shape = "Hexagon"
                    else:
                        shape = "Circle"

                    cv2.drawContours(output, [c], -1, (0, 255, 0), 2)
                    cv2.putText(output, shape, (cX, cY),
                                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)

            self.processed_image = output
            return True
        return False

    def detect_faces(self):
        if self.processed_image is not None:
            gray = cv2.cvtColor(self.processed_image, cv2.COLOR_BGR2GRAY)
            faces = self.face_cascade.detectMultiScale(gray, 1.3, 5)

            output = self.processed_image.copy()
            for (x, y, w, h) in faces:
                cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 2)
                cv2.putText(output, "Face", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

            self.processed_image = output
            return len(faces)
        return 0


class VideoThread(QThread):
    change_pixmap_signal = pyqtSignal(QImage)
    detection_signal = pyqtSignal(object)

    def __init__(self, processor, detection_type=None, video_path=None):
        super().__init__()
        self.processor = processor
        self.detection_type = detection_type
        self.video_path = video_path
        self._run_flag = True
        self.cap = None

    def run(self):
        if self.video_path is None:
            self.cap = cv2.VideoCapture(0)
        else:
            self.cap = cv2.VideoCapture(self.video_path)

        if not self.cap.isOpened():
            self._run_flag = False
            self.emit_error("无法打开视频源")
            return

        fps = self.cap.get(cv2.CAP_PROP_FPS)
        frame_delay = int(1000 / fps)  # 计算每帧间隔时间(毫秒)

        while self._run_flag:
            ret, cv_img = self.cap.read()
            if not ret:
                # 视频播放完毕，重新循环
                if self.video_path:
                    self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
                    continue
                else:
                    break

            if self.detection_type == 'face':
                gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
                faces = self.processor.face_cascade.detectMultiScale(gray, 1.3, 5)
                face_data = [{'location': (x, y, x+w, y+h), 'name': "Face"} for (x, y, w, h) in faces]
                self.detection_signal.emit(face_data)

            qt_img = self.convert_cv_qt(cv_img)
            self.change_pixmap_signal.emit(qt_img)
            self.msleep(frame_delay)  # 控制帧率

        if self.cap:
            self.cap.release()

    def stop(self):
        self._run_flag = False
        self.wait()
        if self.cap:
            self.cap.release()

    def convert_cv_qt(self, cv_img):
        rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb_image.shape
        bytes_per_line = ch * w
        qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)
        return qt_image.scaled(640, 480, Qt.KeepAspectRatio)

    def emit_error(self, message):
        print(f"错误：{message}")


class ImageDisplay(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAlignment(Qt.AlignCenter)
        self.setText('请加载图像')
        self.setStyleSheet("border: 1px solid gray;")
        self.setMinimumSize(400, 400)

        self.image_processor = None
        self.scaled_pixmap = None
        self.drawing_rect = False
        self.rect_start = QPoint()
        self.rect_end = QPoint()

    def set_processor(self, processor):
        self.image_processor = processor

    def paintEvent(self, event):
        super().paintEvent(event)
        if self.image_processor and self.image_processor.processed_image is not None:
            painter = QPainter(self)
            painter.setRenderHint(QPainter.Antialiasing)

            if self.scaled_pixmap:
                painter.drawPixmap(0, 0, self.scaled_pixmap)

            if self.drawing_rect and not self.rect_start.isNull() and not self.rect_end.isNull():
                pen = QPen(Qt.red)
                pen.setWidth(2)
                painter.setPen(pen)
                painter.drawRect(QRect(self.rect_start, self.rect_end))

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and self.image_processor and self.image_processor.processed_image is not None:
            self.drawing_rect = True
            self.rect_start = event.pos()
            self.rect_end = event.pos()
            self.update()

    def mouseMoveEvent(self, event):
        if self.drawing_rect and not self.rect_start.isNull():
            self.rect_end = event.pos()
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.drawing_rect:
            self.drawing_rect = False
            self.rect_end = event.pos()

            if not self.rect_start.isNull() and not self.rect_end.isNull():
                rect = QRect(self.rect_start, self.rect_end).normalized()
                self.image_processor.rect_start = self.rect_start
                self.image_processor.rect_end = self.rect_end
                self.image_processor.drawing_rect = True

                # 发送裁剪信号
                if hasattr(self.parent(), 'crop_requested'):
                    self.parent().crop_requested.emit(rect)

            self.update()

    def update_image(self):
        if self.image_processor and self.image_processor.processed_image is not None:
            qimage = self.image_processor.get_qimage()
            pixmap = QPixmap.fromImage(qimage)

            # 缩放图片以适应标签大小
            self.scaled_pixmap = pixmap.scaled(
                self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)

            self.setPixmap(self.scaled_pixmap)
            self.setFixedSize(self.scaled_pixmap.size())
            self.updateGeometry()
            self.update()


class VideoDisplay(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAlignment(Qt.AlignCenter)
        self.setText('视频预览')
        self.setStyleSheet("border: 1px solid gray;")
        self.setMinimumSize(640, 480)
        self.face_data = []

    def paintEvent(self, event):
        super().paintEvent(event)
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        for data in self.face_data:
            left, top, right, bottom = data['location']
            name = data['name']

            # 绘制人脸框
            pen = QPen(Qt.green)
            pen.setWidth(2)
            painter.setPen(pen)
            painter.drawRect(left, top, right - left, bottom - top)

            # 绘制名字标签
            painter.fillRect(left, top - 20, right - left, 20, Qt.green)
            painter.setPen(Qt.white)
            painter.drawText(left + 5, top - 5, name)

    def update_frame(self, qimage):
        pixmap = QPixmap.fromImage(qimage)
        self.setPixmap(pixmap)
        self.setFixedSize(pixmap.size())
        self.updateGeometry()
        self.update()

    def update_face_data(self, face_data):
        self.face_data = face_data
        self.update()


class ImageProcessingTab(QWidget):
    crop_requested = pyqtSignal(QRect)

    def __init__(self, processor, parent=None):
        super().__init__(parent)
        self.processor = processor
        self.initUI()

    def initUI(self):
        # 主布局
        main_layout = QVBoxLayout(self)

        # 创建分割器
        splitter = QSplitter(Qt.Horizontal)

        # 左侧图像显示区域
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)

        self.image_display = ImageDisplay()
        self.image_display.set_processor(self.processor)
        left_layout.addWidget(self.image_display)

        splitter.addWidget(left_widget)

        # 右侧控制面板
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)

        # 旋转控制
        rotate_group = QGroupBox("旋转")
        rotate_layout = QHBoxLayout()

        self.rotate_spin = QSpinBox()
        self.rotate_spin.setRange(-360, 360)
        self.rotate_spin.setValue(0)
        self.rotate_spin.setSuffix("°")

        rotate_btn = QPushButton("旋转")
        rotate_btn.clicked.connect(self.rotate_image)

        rotate_layout.addWidget(QLabel("角度:"))
        rotate_layout.addWidget(self.rotate_spin)
        rotate_layout.addWidget(rotate_btn)
        rotate_group.setLayout(rotate_layout)

        # 缩放控制
        scale_group = QGroupBox("缩放")
        scale_layout = QHBoxLayout()

        self.scale_spin = QDoubleSpinBox()
        self.scale_spin.setRange(0.1, 5.0)
        self.scale_spin.setValue(1.0)
        self.scale_spin.setSingleStep(0.1)
        self.scale_spin.setSuffix("x")

        scale_btn = QPushButton("缩放")
        scale_btn.clicked.connect(self.scale_image)

        scale_layout.addWidget(QLabel("比例:"))
        scale_layout.addWidget(self.scale_spin)
        scale_layout.addWidget(scale_btn)
        scale_group.setLayout(scale_layout)

        # 亮度/对比度控制
        brightness_group = QGroupBox("亮度/对比度")
        brightness_layout = QVBoxLayout()

        brightness_layout.addWidget(QLabel("亮度:"))
        self.brightness_slider = QSlider(Qt.Horizontal)
        self.brightness_slider.setRange(-100, 100)
        self.brightness_slider.setValue(0)
        brightness_layout.addWidget(self.brightness_slider)

        brightness_layout.addWidget(QLabel("对比度:"))
        self.contrast_slider = QSlider(Qt.Horizontal)
        self.contrast_slider.setRange(-100, 100)
        self.contrast_slider.setValue(0)
        brightness_layout.addWidget(self.contrast_slider)

        brightness_btn = QPushButton("应用")
        brightness_btn.clicked.connect(self.adjust_brightness_contrast)
        brightness_layout.addWidget(brightness_btn)

        brightness_group.setLayout(brightness_layout)

        # 饱和度控制
        saturation_group = QGroupBox("饱和度")
        saturation_layout = QHBoxLayout()

        self.saturation_spin = QDoubleSpinBox()
        self.saturation_spin.setRange(0.1, 5.0)
        self.saturation_spin.setValue(1.0)
        self.saturation_spin.setSingleStep(0.1)

        saturation_btn = QPushButton("应用")
        saturation_btn.clicked.connect(self.adjust_saturation)

        saturation_layout.addWidget(QLabel("饱和度:"))
        saturation_layout.addWidget(self.saturation_spin)
        saturation_layout.addWidget(saturation_btn)
        saturation_group.setLayout(saturation_layout)

        # 平滑处理控制
        smooth_group = QGroupBox("平滑处理")
        smooth_layout = QVBoxLayout()

        self.smooth_combo = QComboBox()
        self.smooth_combo.addItems(["Gaussian", "Median", "Bilateral"])

        self.kernel_spin = QSpinBox()
        self.kernel_spin.setRange(1, 15)
        self.kernel_spin.setValue(3)
        self.kernel_spin.setSingleStep(2)

        smooth_btn = QPushButton("应用")
        smooth_btn.clicked.connect(self.smooth_image)

        smooth_layout.addWidget(QLabel("方法:"))
        smooth_layout.addWidget(self.smooth_combo)
        smooth_layout.addWidget(QLabel("核大小:"))
        smooth_layout.addWidget(self.kernel_spin)
        smooth_layout.addWidget(smooth_btn)
        smooth_group.setLayout(smooth_layout)

        # 形状识别控制
        shape_group = QGroupBox("形状识别")
        shape_layout = QHBoxLayout()

        shape_btn = QPushButton("检测形状")
        shape_btn.clicked.connect(self.detect_shapes)

        shape_layout.addWidget(shape_btn)
        shape_group.setLayout(shape_layout)

        # 裁剪控制
        crop_group = QGroupBox("裁剪")
        crop_layout = QHBoxLayout()

        crop_btn = QPushButton("裁剪选中区域")
        crop_btn.clicked.connect(self.crop_image)

        crop_layout.addWidget(crop_btn)
        crop_group.setLayout(crop_layout)

        # 添加所有控制组到右侧布局
        right_layout.addWidget(rotate_group)
        right_layout.addWidget(scale_group)
        right_layout.addWidget(brightness_group)
        right_layout.addWidget(saturation_group)
        right_layout.addWidget(smooth_group)
        right_layout.addWidget(shape_group)
        right_layout.addWidget(crop_group)

        # 添加上下文信息显示
        self.info_label = QLabel("图像信息将显示在这里")
        right_layout.addWidget(self.info_label)

        # 添加上下文信息显示
        right_layout.addStretch()

        splitter.addWidget(right_widget)

        # 设置分割器的初始大小
        splitter.setSizes([800, 300])

        main_layout.addWidget(splitter)

        # 连接裁剪信号
        self.crop_requested.connect(self.on_crop_requested)

    def update_image(self):
        self.image_display.update_image()
        if self.processor.processed_image is not None:
            h, w = self.processor.processed_image.shape[:2]
            self.info_label.setText(f"图像尺寸: {w}x{h}")

    def rotate_image(self):
        angle = self.rotate_spin.value()
        if self.processor.rotate(angle):
            self.update_image()

    def scale_image(self):
        factor = self.scale_spin.value()
        if self.processor.scale(factor):
            self.update_image()

    def adjust_brightness_contrast(self):
        brightness = self.brightness_slider.value()
        contrast = self.contrast_slider.value()
        if self.processor.adjust_brightness_contrast(brightness, contrast):
            self.update_image()

    def adjust_saturation(self):
        value = self.saturation_spin.value()
        if self.processor.adjust_saturation(value):
            self.update_image()

    def smooth_image(self):
        method = self.smooth_combo.currentText()
        kernel_size = self.kernel_spin.value()
        if kernel_size % 2 == 0:
            kernel_size += 1  # 确保核大小为奇数
            self.kernel_spin.setValue(kernel_size)
        if self.processor.smooth(method, kernel_size):
            self.update_image()

    def detect_shapes(self):
        if self.processor.detect_shapes():
            self.update_image()

    def on_crop_requested(self, rect):
        # 存储矩形区域，用于后续处理
        self.crop_rect = rect

    def crop_image(self):
        if hasattr(self, 'crop_rect'):
            if self.processor.crop(self.crop_rect):
                self.update_image()


class FaceRecognitionTab(QWidget):
    def __init__(self, processor, parent=None):
        super().__init__(parent)
        self.processor = processor
        self.initUI()

    def initUI(self):
        # 主布局
        main_layout = QVBoxLayout(self)

        # 创建分割器
        splitter = QSplitter(Qt.Horizontal)

        # 左侧图像显示区域
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)

        self.image_display = ImageDisplay()
        self.image_display.set_processor(self.processor)
        left_layout.addWidget(self.image_display)

        splitter.addWidget(left_widget)

        # 右侧控制面板
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)

        # 人脸识别控制
        recognition_group = QGroupBox("人脸检测")
        recognition_layout = QVBoxLayout()

        recognize_btn = QPushButton("检测人脸")
        recognize_btn.clicked.connect(self.detect_faces)

        recognition_layout.addWidget(recognize_btn)
        recognition_group.setLayout(recognition_layout)

        # 添加上下文信息显示
        self.info_label = QLabel("人脸检测结果将显示在这里")
        right_layout.addWidget(recognition_group)
        right_layout.addWidget(self.info_label)
        right_layout.addStretch()

        splitter.addWidget(right_widget)

        # 设置分割器的初始大小
        splitter.setSizes([800, 300])

        main_layout.addWidget(splitter)

    def update_image(self):
        self.image_display.update_image()

    def detect_faces(self):
        if self.processor.processed_image is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        face_count = self.processor.detect_faces()
        if face_count >= 0:
            self.update_image()
            self.info_label.setText(f"检测到 {face_count} 个人脸")
            QMessageBox.information(self, "成功", f"人脸检测完成，共检测到 {face_count} 个人脸")
        else:
            QMessageBox.warning(self, "失败", "人脸检测失败")


class VideoProcessingTab(QWidget):
    def __init__(self, processor, parent=None):
        super().__init__(parent)
        self.processor = processor
        self.video_thread = None
        self.video_path = None
        self.initUI()

    def initUI(self):
        # 主布局
        main_layout = QVBoxLayout(self)

        # 创建分割器
        splitter = QSplitter(Qt.Vertical)

        # 上侧视频显示区域
        upper_widget = QWidget()
        upper_layout = QVBoxLayout(upper_widget)

        # 视频控制按钮
        control_layout = QHBoxLayout()

        self.open_video_btn = QPushButton("打开视频文件")
        self.open_video_btn.clicked.connect(self.open_video_file)
        control_layout.addWidget(self.open_video_btn)

        self.play_btn = QPushButton("播放")
        self.play_btn.clicked.connect(self.toggle_play)
        self.play_btn.setEnabled(False)

        self.stop_btn = QPushButton("停止")
        self.stop_btn.clicked.connect(self.stop_video)
        self.stop_btn.setEnabled(False)

        self.face_detection_btn = QPushButton("人脸检测")
        self.face_detection_btn.clicked.connect(self.toggle_face_detection)
        self.face_detection_btn.setEnabled(False)

        self.save_frame_btn = QPushButton("保存当前帧")
        self.save_frame_btn.clicked.connect(self.save_current_frame)
        self.save_frame_btn.setEnabled(False)

        control_layout.addWidget(self.play_btn)
        control_layout.addWidget(self.stop_btn)
        control_layout.addWidget(self.face_detection_btn)
        control_layout.addWidget(self.save_frame_btn)

        upper_layout.addLayout(control_layout)

        self.video_display = VideoDisplay()
        upper_layout.addWidget(self.video_display)

        splitter.addWidget(upper_widget)

        # 下侧控制面板
        lower_widget = QWidget()
        lower_layout = QVBoxLayout(lower_widget)

        # 添加上下文信息显示
        self.info_label = QLabel("视频信息将显示在这里")
        lower_layout.addWidget(self.info_label)

        splitter.addWidget(lower_widget)

        # 设置分割器的初始大小
        splitter.setSizes([600, 200])

        main_layout.addWidget(splitter)

    def open_video_file(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "打开视频文件", "", "视频文件 (*.mp4 *.avi *.mkv *.mov)")
        if file_path:
            self.video_path = file_path
            self.play_btn.setEnabled(True)
            self.info_label.setText(f"已加载视频：{os.path.basename(file_path)}")

    def toggle_play(self):
        if not self.video_path and not hasattr(self, 'camera_mode'):
            QMessageBox.warning(self, "警告", "请先打开视频文件或切换到摄像头模式")
            return

        if self.video_thread and self.video_thread.isRunning():
            self.stop_video()
        else:
            self.start_video()

    def start_video(self):
        if not hasattr(self, 'camera_mode') or not self.camera_mode:
            if not self.video_path:
                QMessageBox.warning(self, "警告", "请先打开视频文件")
                return

        self.video_thread = VideoThread(self.processor, video_path=self.video_path if not hasattr(self, 'camera_mode') or not self.camera_mode else None)
        self.video_thread.change_pixmap_signal.connect(self.video_display.update_frame)
        self.video_thread.detection_signal.connect(self.video_display.update_face_data)
        self.video_thread.start()

        self.play_btn.setText("暂停")
        self.stop_btn.setEnabled(True)
        self.face_detection_btn.setEnabled(True)
        self.save_frame_btn.setEnabled(True)
        self.info_label.setText(f"正在播放：{os.path.basename(self.video_path) if self.video_path else '摄像头'}")

    def stop_video(self):
        if self.video_thread:
            self.video_thread.stop()
            self.video_thread = None

        self.play_btn.setText("播放")
        self.stop_btn.setEnabled(False)
        self.face_detection_btn.setEnabled(False)
        self.save_frame_btn.setEnabled(False)
        self.info_label.setText("视频已停止")

    def toggle_face_detection(self):
        if self.video_thread:
            if self.video_thread.detection_type == 'face':
                self.video_thread.detection_type = None
                self.face_detection_btn.setText("人脸检测")
                self.info_label.setText("人脸检测已关闭")
            else:
                self.video_thread.detection_type = 'face'
                self.face_detection_btn.setText("关闭人脸检测")
                self.info_label.setText("人脸检测已开启")

    def save_current_frame(self):
        if self.video_display.pixmap() and not self.video_display.pixmap().isNull():
            file_path, _ = QFileDialog.getSaveFileName(
                self, "保存当前帧", f"frame_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png",
                "图像文件 (*.png *.jpg *.jpeg)")
            if file_path:
                self.video_display.pixmap().save(file_path)
                QMessageBox.information(self, "成功", f"当前帧已保存至: {file_path}")


class StitchingTab(QWidget):
    def __init__(self, processor, parent=None):
        super().__init__(parent)
        self.processor = processor
        self.images = []
        self.initUI()

    def initUI(self):
        # 主布局
        main_layout = QVBoxLayout(self)

        # 创建分割器
        splitter = QSplitter(Qt.Horizontal)

        # 左侧图像选择区域
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)

        # 图像列表
        self.image_list = QListWidget()
        left_layout.addWidget(self.image_list)

        # 控制按钮
        btn_layout = QHBoxLayout()

        add_btn = QPushButton("添加图像")
        add_btn.clicked.connect(self.add_images)

        remove_btn = QPushButton("移除所选")
        remove_btn.clicked.connect(self.remove_selected)

        clear_btn = QPushButton("清空")
        clear_btn.clicked.connect(self.clear_images)

        btn_layout.addWidget(add_btn)
        btn_layout.addWidget(remove_btn)
        btn_layout.addWidget(clear_btn)

        left_layout.addLayout(btn_layout)

        # 拼接按钮
        stitch_btn = QPushButton("开始拼接")
        stitch_btn.clicked.connect(self.stitch_images)
        left_layout.addWidget(stitch_btn)

        splitter.addWidget(left_widget)

        # 右侧图像显示区域
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)

        self.image_display = ImageDisplay()
        self.image_display.set_processor(self.processor)
        right_layout.addWidget(self.image_display)

        # 添加上下文信息显示
        self.info_label = QLabel("拼接结果将显示在这里")
        right_layout.addWidget(self.info_label)

        splitter.addWidget(right_widget)

        # 设置分割器的初始大小
        splitter.setSizes([300, 800])

        main_layout.addWidget(splitter)

    def add_images(self):
        file_paths, _ = QFileDialog.getOpenFileNames(
            self, "选择图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)")

        for file_path in file_paths:
            image = cv2.imread(file_path)
            if image is not None:
                self.images.append(image)
                file_name = os.path.basename(file_path)
                self.image_list.addItem(file_name)

        self.update_info()

    def remove_selected(self):
        for item in self.image_list.selectedItems():
            index = self.image_list.row(item)
            if 0 <= index < len(self.images):
                del self.images[index]
                self.image_list.takeItem(index)

        self.update_info()

    def clear_images(self):
        self.images = []
        self.image_list.clear()
        self.update_info()

    def stitch_images(self):
        if len(self.images) < 2:
            QMessageBox.warning(self, "警告", "请至少选择两张图像进行拼接")
            return

        stitcher = cv2.Stitcher_create() if hasattr(cv2, 'Stitcher_SCANS') else cv2.Stitcher_create(cv2.Stitcher_SCANS)
        status, stitched = stitcher.stitch(self.images)

        if status == 0:
            self.processor.processed_image = stitched
            self.image_display.update_image()
            QMessageBox.information(self, "成功", "图像拼接完成")
        else:
            error_messages = {
                cv2.Stitcher_ERR_NEED_MORE_IMGS: "需要更多图像",
                cv2.Stitcher_ERR_HOMOGRAPHY_EST_FAIL: "单应性矩阵估计失败",
                cv2.Stitcher_ERR_CAMERA_PARAMS_ADJUST_FAIL: "相机参数调整失败"
            }
            error_msg = error_messages.get(status, f"未知错误 (状态码: {status})")
            QMessageBox.warning(self, "失败", f"图像拼接失败: {error_msg}")

    def update_info(self):
        count = len(self.images)
        self.info_label.setText(f"已选择 {count} 张图像")


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.processor = ImageProcessor()
        self.initUI()

    def initUI(self):
        # 设置窗口标题和大小
        self.setWindowTitle('图像处理与计算机视觉平台')
        self.setGeometry(100, 100, 1200, 800)

        # 创建菜单栏
        self.create_menu_bar()

        # 创建工具栏
        self.create_tool_bar()

        # 创建状态栏
        self.statusBar().showMessage('就绪')

        # 创建主选项卡
        self.tabs = QTabWidget()

        # 添加图像处理选项卡
        self.image_tab = ImageProcessingTab(self.processor)
        self.tabs.addTab(self.image_tab, "图像处理")

        # 添加人脸识别选项卡
        self.face_tab = FaceRecognitionTab(self.processor)
        self.tabs.addTab(self.face_tab, "人脸检测")

        # 添加视频处理选项卡
        self.video_tab = VideoProcessingTab(self.processor)
        self.tabs.addTab(self.video_tab, "视频处理")

        # 添加图像拼接选项卡
        self.stitching_tab = StitchingTab(self.processor)
        self.tabs.addTab(self.stitching_tab, "图像拼接")

        # 设置中心部件
        self.setCentralWidget(self.tabs)

    def create_menu_bar(self):
        menubar = self.menuBar()

        # 文件菜单
        file_menu = menubar.addMenu('文件')

        # 打开图像动作
        open_action = QAction('打开图像', self)
        open_action.setShortcut('Ctrl+O')
        open_action.triggered.connect(self.open_image)
        file_menu.addAction(open_action)

        # 保存图像动作
        save_action = QAction('保存图像', self)
        save_action.setShortcut('Ctrl+S')
        save_action.triggered.connect(self.save_image)
        file_menu.addAction(save_action)

        file_menu.addSeparator()

        # 退出动作
        exit_action = QAction('退出', self)
        exit_action.setShortcut('Ctrl+Q')
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # 编辑菜单
        edit_menu = menubar.addMenu('编辑')

        # 撤销动作
        undo_action = QAction('撤销', self)
        undo_action.setShortcut('Ctrl+Z')
        undo_action.triggered.connect(self.undo)
        edit_menu.addAction(undo_action)

        # 帮助菜单
        help_menu = menubar.addMenu('帮助')

        # 关于动作
        about_action = QAction('关于', self)
        about_action.triggered.connect(self.about)
        help_menu.addAction(about_action)

    def create_tool_bar(self):
        toolbar = QToolBar('工具栏')
        self.addToolBar(toolbar)

        # 打开图像按钮
        open_btn = QAction('打开图像', self)
        open_btn.triggered.connect(self.open_image)
        toolbar.addAction(open_btn)

        # 保存图像按钮
        save_btn = QAction('保存图像', self)
        save_btn.triggered.connect(self.save_image)
        toolbar.addAction(save_btn)

        toolbar.addSeparator()

        # 撤销按钮
        undo_btn = QAction('撤销', self)
        undo_btn.triggered.connect(self.undo)
        toolbar.addAction(undo_btn)

    def open_image(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)")

        if file_path:
            if self.processor.load_image(file_path):
                self.update_current_tab_image()
                self.statusBar().showMessage(f"已加载图像: {os.path.basename(file_path)}")
            else:
                QMessageBox.warning(self, "警告", "无法加载图像")

    def save_image(self):
        if self.processor.processed_image is None:
            QMessageBox.warning(self, "警告", "没有可保存的图像")
            return

        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)")

        if file_path:
            try:
                cv2.imwrite(file_path, self.processor.processed_image)
                self.statusBar().showMessage(f"图像已保存至: {file_path}")
            except Exception as e:
                QMessageBox.warning(self, "警告", f"保存图像失败: {str(e)}")

    def undo(self):
        if self.processor.original_image is not None:
            self.processor.processed_image = self.processor.original_image.copy()
            self.update_current_tab_image()
            self.statusBar().showMessage("已恢复原始图像")

    def about(self):
        QMessageBox.about(self, "关于", "图像处理与计算机视觉平台\n\n"
                                      "这是一个基于PyQt5和OpenCV的图像处理平台，\n"
                                      "支持基本图像处理、人脸检测、视频处理和图像拼接等功能。")

    def update_current_tab_image(self):
        current_index = self.tabs.currentIndex()
        if current_index == 0:  # 图像处理选项卡
            self.image_tab.update_image()
        elif current_index == 1:  # 人脸检测选项卡
            self.face_tab.update_image()
        elif current_index == 3:  # 图像拼接选项卡
            self.stitching_tab.image_display.update_image()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())